package org.reborn.model

import org.reborn.model.TileLOD._
import org.reborn.Constants._
import org.reborn.math._
import org.reborn.math.util._


object TileUtil {
  
  /**
   * Convert 2D world coordinates to a Tile coordinate with the specified Level of Detail
   */
  def worldCoordToTile(worldPos:VectorF,lod:TileLOD) = {
    val x = (worldPos(0) / TILE_LENGTH).asInstanceOf[Int] / SMALL_TILES_PER_TILE_LENGTH(lod)
    val y = (worldPos(1) / TILE_LENGTH).asInstanceOf[Int] / SMALL_TILES_PER_TILE_LENGTH(lod)
    new VectorI(List(x,y))
  }
  
  /**
   * Find the Tile coordinates of a lower Level of Detail tile, that is an ancestor
   * of this Tile
   */
  def ancestorTile(tileKey:(Int,Int,TileLOD),ancestorLOD:TileLOD) = {
    require(LOD_INDEX(tileKey._3) < LOD_INDEX(ancestorLOD))
    val x = tileKey._1 / (TILES_PER_BIG_TILE_LENGTH(tileKey._3) / TILES_PER_BIG_TILE_LENGTH(ancestorLOD))
    val y = tileKey._2 / (TILES_PER_BIG_TILE_LENGTH(tileKey._3) / TILES_PER_BIG_TILE_LENGTH(ancestorLOD))
    new VectorI(List(x,y))
  }
  
  /**
   * Find the minumum Tile index at the specified LOD, based on the current player's Lowest
   * LOD tile index and the number of High LOD Tiles to display
   */
  def minTileIndex(lowestPlayerTilePos:Int,numHighDetailTiles:Int,lod:TileLOD) = {
    if(lod == Lowest) lowestPlayerTilePos - (DISPLAYABLE_BIG_TILES / 2)
    else (lowestPlayerTilePos - numHighDetailTiles - LOD_INDEX(lod)) * TILES_PER_BIG_TILE_LENGTH(lod)
  }
  
  /**
   * Find the maximum Tile index at the specified LOD, based on the current player's Lowest
   * LOD tile index and the number of High LOD Tiles to display
   */
  def maxTileIndex(lowestPlayerTilePos:Int,numHighDetailTiles:Int,lod:TileLOD) = {
    if(lod == Lowest) lowestPlayerTilePos + (DISPLAYABLE_BIG_TILES / 2) + 1
    else (lowestPlayerTilePos + numHighDetailTiles + LOD_INDEX(lod) + 1) * TILES_PER_BIG_TILE_LENGTH(lod)
  }
  
  /**
   * Calculates and creates empty Tile objects that are required for the world to be displayed.
   * This calculation is done based on player position, the distance between Level of Detail
   * levels, and the least detailed LOD that can be displayed (0 being the lowest LOD)
   */
  def initVisibleTiles(playerPos:VectorF,numHighDetailTiles:Int):Map[(Int,Int,TileLOD),Tile] = {
    require(numHighDetailTiles >= 0)
    
    val lowestTile = worldCoordToTile(playerPos,Lowest)
    val lowestLOD = LOD_INDEX(Lowest)
    val highestLOD = LOD_INDEX(High)
    var tiles:Map[(Int,Int,TileLOD),Tile] = Map()
        
    def setupDetailTiles(lod:TileLOD):Unit = {
      val parentLod = INDEX_LOD.get(LOD_INDEX(lod) + 1)
      val childLod = INDEX_LOD.get(LOD_INDEX(lod) - 1)
      val minX = minTileIndex(lowestTile(0),numHighDetailTiles,lod)
      val maxX = maxTileIndex(lowestTile(0),numHighDetailTiles,lod)
      val minY = minTileIndex(lowestTile(1),numHighDetailTiles,lod)
      val maxY = maxTileIndex(lowestTile(1),numHighDetailTiles,lod)
      
      //Do this for all tiles in range for this LOD
      //We depend on the order that (x,y) pairs are generated
      //so might need to look into how/if order is preserved inside
      //a for comprehension
      for(x <- List.range(minX,maxX);
          y <- List.range(minY,maxY)){
            //Create a new Tile object
            val tile = new Tile(x,y,lod)
            val tileKey = (x,y,lod) 
            
            //If there is a parent Tile for this tile, then set it,
            //otherwise, it means this is the Lowest LOD
            if(parentLod.isDefined){
              val parentLodVal = parentLod.get
              val ancestor = ancestorTile(tileKey,parentLodVal)
              tile.parent = tiles.get((ancestor(0),ancestor(1),parentLodVal))
            }
            
            //if Tile's index on the X axis is greater than the min value,
            //then set Tile's left sibling (and visa versa) 
            if(x > minX ){
              val leftTile = tiles((x-1,y,lod))
              leftTile.right = Some(tile)
              tile.left = Some(leftTile)
            }
            
            //if Tile's index on the Y axis is greater than the min value,
            //then set Tile's top sibling (and visa versa)
            if(y > minY){
              val topTile = tiles((x,y-1,lod))
              topTile.bottom = Some(tile)
              tile.top = Some(topTile)
            }
            
            //Add this tile to the map of tiles
            tiles += ((tileKey,tile))
          }
      
      //Recursively call this function on child LODs until we get to 
      //the most detailed LOD
      if(childLod.isDefined) setupDetailTiles(childLod.get)
    }
    
    //Call the setupDetailTiles recursive method on the Lowest LOD
    setupDetailTiles(Lowest)
    
    //return the map
    tiles
  }
}
